﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using CoreLibrary.Repo;
using Entities.DataBase.Persistent;
using Entities.DataBase.Sql;

namespace Entities.DataBase.Impl {

    public class ProductCatalogRepository : BaseRepository<ProductCatalog>, IProductCatalogRepository {

        private static readonly Func<DataContext, int, ProductCatalog>
          LinqGetProductCatalog = CompiledQuery.Compile((DataContext context, int id)
              => context.GetTable<ProductCatalog>().SingleOrDefault(c => c.Id == id));

        private static readonly Func<DataContext, int, ProductCatalog>
          LinqGetParentProductCatalog = CompiledQuery.Compile((DataContext context, int pid)
              => context.GetTable<ProductCatalog>().SingleOrDefault(c => c.Id == pid));

        private static readonly Func<DataContext, string, ProductCatalog>
          LinqGetProductCatalogByName = CompiledQuery.Compile((DataContext context, string name)
              => context.GetTable<ProductCatalog>().SingleOrDefault(c => c.Name.Equals(name)));

        private static readonly Func<DataContext, int, IQueryable<ProductCatalog>>
           LinqGetChildren = CompiledQuery.Compile(
            (DataContext context, int parentId) =>
                context.GetTable<ProductCatalog>().Where(a => a.PID == parentId)
                                                  .OrderBy(a => a.Id)
        );

        private static readonly Func<DataContext,IQueryable<ProductCatalog>>
           LinqGetAllLeafs = CompiledQuery.Compile(
            (DataContext context) =>
                context.GetTable<ProductCatalog>().Where(a => a.RID == a.LID + 1)
                                                  .OrderBy(a => a.Id)
        );

        private static readonly Func<DataContext, int, int, IQueryable<ProductCatalog>>
           LinqGetDescendants = CompiledQuery.Compile(
            (DataContext context, int from, int to) =>
                context.GetTable<ProductCatalog>().Where(a => a.LID > from && a.LID <= to)
                                                  .OrderBy(a => a.LID)
        );

        private const int Root = 1;

        public override string TableName {
            get {
                return "D_PRODUCT_CATALOG";
            }
        }

        public void InitRoot() {
            Execute((context, transaction) => {
                var root = LinqGetProductCatalog(context, Root);
                if (root != null) {
                    transaction.Complete();
                    return;
                }

                context.GetTable<ProductCatalog>().InsertOnSubmit(
                    new ProductCatalog {
                        Id          = Root,
                        Enabled     = true,
                        ListOrder   = 0,
                        Name        = "Product Catalog",
                        Cname       = "产品目录",
                        Description = "产品总目录"
                    }
                );
                context.SubmitChanges();
                transaction.Complete();
            });
        }

        public ProductCatalog GetRoot() {
            return Execute(context => LinqGetProductCatalog(context, Root));
        }

        public void DeleteCatalog(int id) {
            if (id <= 1) {
                throw new Exception("root can not be deleted.");
            }

            var current = GetCatalog(id);
            if (current == null) {
                throw new Exception("category does not exist.");
            }

            if (!current.IsLeaf) {
                 throw new Exception("Only Leaf Catalog can be deleted");
            }

            var width   = current.RID - current.LID + 1;
            var succeed = Execute(new UpdateBeforeDelete(width, current.RID, TableName));
            if (succeed) {
                Execute((context, transaction) => {
                    var cat = LinqGetProductCatalog(context, id);
                    if (cat == null) {
                        transaction.Complete();
                        return;
                    }

                    var table = context.GetTable<ProductCatalog>();
                    table.DeleteOnSubmit(cat);
                    context.SubmitChanges();
                    transaction.Complete();
                });
            }
        }

        public ProductCatalog AppendCatalog(int parentId, ProductCatalog catalogItem) {
            var parent = Execute(cxt => LinqGetProductCatalog(cxt,parentId));
            if (parent == null) {
                throw new Exception("Parent not exist.");
            }

            var cat = GetCatalogByName(catalogItem.Name);
            if (cat != null) {
                throw new Exception("Catalog Name Already Exist.");
            }

            catalogItem.PID   = parent.Id;
            catalogItem.Level = parent.Level + 1;
            bool succeed;
            if (parent.IsLeaf) {
                catalogItem.LID = parent.LID + 1;
                catalogItem.RID = parent.LID + 2;
                succeed = Execute(new UpdateBeforeAppendToFirst(parent.LID, TableName));
            } 
            else {
                catalogItem.LID = parent.RID;
                catalogItem.RID = parent.RID + 1;
                succeed = Execute(new UpdateBeforeAppendToLast(parent.RID, TableName));
            }

            if (succeed) {
                Execute((cxt,ts) => {
                    var table = cxt.GetTable<ProductCatalog>();
                    table.InsertOnSubmit(catalogItem);
                    cxt.SubmitChanges();
                    ts.Complete();
                });
            }

            return GetCatalog(catalogItem.Id);
        }

        public void InsertCatalogAfter(int catalogId, ProductCatalog catalogItem) {
            var cat = Execute(cxt => LinqGetProductCatalog(cxt, catalogId));

            if (cat == null) {
                throw new Exception("Catalog not exist.");
            }

            var catByName = GetCatalogByName(catalogItem.Name);
            if (catByName != null) {
                throw new Exception("Catalog with this name already exist.");
            }

            catalogItem.PID   = cat.PID;
            catalogItem.LID   = cat.LID + 1;
            catalogItem.RID   = cat.LID + 2;
            catalogItem.Level = cat.Level;

            if (Execute(new UpdateBeforeInsertAfter(cat.RID, TableName))) {
                Execute((cxt, ts) => {
                    var table = cxt.GetTable<ProductCatalog>();
                    table.InsertOnSubmit(catalogItem);
                    cxt.SubmitChanges();
                    ts.Complete();
                });
            }
        }

        public ProductCatalog GetCatalog(int catId) {
            return Execute(context => LinqGetProductCatalog(context,catId));
        }

        public ProductCatalog GetCatalogByName(string name) {
            return Execute(context => LinqGetProductCatalogByName(context, name));
        }

        public ProductCatalog GetParentCatalog(int catId) {
            var catalog = GetCatalog(catId);
            return Execute(cxt => LinqGetParentProductCatalog(cxt, catalog.PID));
        }

        public List<ProductCatalog> GetPath(int catId) {
            return Execute(new SelectPath(catId, TableName));
        }

        public List<ProductCatalog> GetChildren(int catId) {
            return Execute(context => {
                var parent = LinqGetProductCatalog(context, catId);
                if (parent == null) {
                    throw new Exception("Parent not exists.");
                }

                var cats = LinqGetChildren(context, catId);
                return cats.ToList();
            });
        }

        public List<ProductCatalog> GetDescendants(int catId) {
            return Execute(context => {
                var cat = LinqGetProductCatalog(context, catId);
                if (cat == null) {
                    throw new Exception("Catalog #"+catId +" not exist");
                }
                return LinqGetDescendants(context, cat.LID, cat.RID).ToList();
            });
        }

        public List<ProductCatalog> GetLeafCatalogs() {
            return Execute(context => LinqGetAllLeafs(context).ToList());
        }

        public void UpdateCatalog(int catId, string name, string cName, string description) {
            Execute((context, ts) => {
                var cat = LinqGetProductCatalog(context, catId);
                if (cat == null) {
                    throw new Exception("Catalog #"+catId + " doest't exist.");
                }

                if (!string.IsNullOrEmpty(name) && !cat.Name.Equals(name)) {
                     cat.Name = name;
                }

                if (!string.IsNullOrEmpty(cName) && !cat.Cname.Equals(cName)) {
                     cat.Cname = cName;
                }

                if (!string.IsNullOrEmpty(description)) {
                    cat.Description = description;
                }

                cat.DateUpdated = DateTime.UtcNow;
                context.SubmitChanges();
                ts.Complete();
            });
        }
    }
}